package com.gome.boot.config.elasticsearch.cluster;

import com.gome.boot.common.logger.BaseLogger;
import com.gome.boot.config.elasticsearch.cluster.selector.SelectClusterByRandom;
import com.gome.boot.config.elasticsearch.search.ListQueryResult;
import com.gome.boot.config.elasticsearch.search.SearchCondition;
import com.gome.boot.config.elasticsearch.tools.IndexTool;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import org.elasticsearch.action.delete.DeleteRequest;
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.client.Client;
import org.springframework.data.elasticsearch.core.query.StringQuery;
import org.springframework.util.Assert;

import java.lang.reflect.ParameterizedType;
import java.util.List;
import java.util.Map;

/**
 * ES多集群客户端.
 *
 * @author baoxiufeng
 */
public class MultiClusterClient extends BaseLogger {

    /** 单例对象 */
    private volatile static MultiClusterClient client = null;
    /** ES集群客户端列表 */
    private List<ClusterClient> clients = Lists.newArrayList();
    /** ES集群客户端集合 */
    private Map<String, ClusterClient> clientMap = Maps.newHashMap();
    /** 默认ES多集群选择器 */
    private MultiClusterSelector defaultClusterSelector = new SelectClusterByRandom();
    /** ES多集群选择器 */
    private MultiClusterSelector clusterSelector;
    /** 是否多集群模式*/
    private boolean multiple = false;

    /**
     * 私有构造方法.
     */
    private MultiClusterClient() {
    }

    /**
     * 获取多集群客户端实例.
     *
     * @return 多集群客户端实例
     */
    public static MultiClusterClient getInstance() {
        if (client == null) {
            synchronized (MultiClusterClient.class) {
                if (client == null) {
                    client = new MultiClusterClient();
                }
            }
        }
        return client;
    }

    /**
     * 根据条件查询指定类型的索引对象信息.
     *
     * @param searchQuery 查询条件
     * @param clazz 查询结果类
     * @param <T> 查询结果对象类型
     * @return 查询结果对象
     */
    public <T> T get(StringQuery searchQuery, Class<T> clazz) {
        return selectClient(null).get(searchQuery, clazz);
    }

    /**
     * 根据条件查询指定类型的索引对象信息.
     *
     * @param condition 查询条件
     * @param clazz 查询结果类
     * @param <T> 查询结果对象类型
     * @return 查询结果对象
     */
    public <T> T get(SearchCondition condition, Class<T> clazz) {
        return selectClient(condition.getHashKey()).get(condition, clazz);
    }

    /**
     * 根据条件查询指定类型的索引对象信息列表.
     *
     * @param condition 查询条件
     * @param clazz 查询结果类
     * @param <T> 查询结果对象类型
     * @return 查询结果对象列表
     */
    public <T> List<T> getList(SearchCondition condition, Class<T> clazz) {
        return selectClient(condition.getHashKey()).getList(condition, clazz);
    }

    /**
     * 根据条件查询指定类型的索引对象信息列表及分页信息.
     *
     * @param condition 查询条件
     * @param clazz 查询结果类
     * @param <T> 查询结果对象类型
     * @return 查询结果对象列表及分页信息
     */
    public <T> ListQueryResult<T> getListResult(SearchCondition condition, Class<T> clazz) {
        return selectClient(condition.getHashKey()).getListResult(condition, clazz);
    }

    /**
     * 根据条件查询指定类型的索引对象信息数量.
     *
     * @param condition 查询条件
     * @param clazz 查询结果类
     * @param <T> 查询结果对象类型
     * @return 查询结果对象列表
     */
    public <T> Long getCount(SearchCondition condition, Class<T> clazz) {
        return selectClient(condition.getHashKey()).getCount(condition, clazz);
    }

    /**
     * 保存/更新索引对象信息请求.
     *
     * @param request 索引对象保存/更新请求
     * @return 保存/更新结果
     */
    public boolean save(IndexRequest request) {
        return save(null, request);
    }

    /**
     * 保存/更新索引对象信息请求.
     *
     * @param hashKey 集群HASH值（预留参数，暂不使用）
     * @param request 索引对象保存/更新请求
     * @return 保存/更新结果
     */
    public boolean save(String hashKey, IndexRequest request) {
        Assert.hasText(request.routing(), "The index selector must not be empty.");
        // 多集群写入处理(只要有一个成功则成功)
        boolean ok = false;
        for (ClusterClient client : clients) {
            try {
                if (client.save(request)) {
                    ok = true;
                } else {
                    MultiClusterLogger.norLog(client.getName(), request.index(), request.type(), request.routing(), request.id());
                }
            } catch (Exception e) {
                logger.error("Save index request failure.", e);
                MultiClusterLogger.expLog(client.getName(), request.index(), request.type(), request.routing(), request.id());
            }
        }
        return ok;
    }

    /**
     * 删除索引对象信息请求.
     *
     * @param request 索引对象删除请求
     * @return 删除结果
     */
    public boolean delete(DeleteRequest request) {
        return delete(null, request);
    }

    /**
     * 删除索引对象信息请求.
     *
     * @param hashKey 集群HASH值（预留参数，暂不使用）
     * @param request 索引对象删除请求
     * @return 删除结果
     */
    public boolean delete(String hashKey, DeleteRequest request) {
        Assert.hasText(request.id(), "The delete index doc id must not be empty.");
        Assert.hasText(request.type(), "The delete index type must not be empty.");
        Assert.hasText(request.index(), "The delete index name must not be empty.");
        // 多集群删除处理(只要有一个成功则成功)
        boolean ok = false;
        for (ClusterClient client : clients) {
            try {
                if (client.delete(request)) {
                    ok = true;
                } else {
                    MultiClusterLogger.norLog(client.getName(), request.index(), request.type(), request.routing(), request.id());
                }
            } catch (Exception e) {
                logger.error("Delete index request failure.", e);
                MultiClusterLogger.expLog(client.getName(), request.index(), request.type(), request.routing(), request.id());
            }
        }
        return ok;
    }

    /**
     * 批量保存/更新索引对应信息.
     *
     * @param requestList 保存/更新索引对象请求列表
     * @return 批量保存/更新结果
     */
    public boolean bulk(List<IndexRequest> requestList) {
        // 多集群写入处理(只要有一个成功则成功)
        boolean ok = false;
        for (ClusterClient client : clients) {
            try {
                if (client.bulk(client.getName(), requestList)) {
                    ok = true;
                }
            } catch (Exception e) {
                ok = false;
                logger.error("Bulk index request failure.", e);
            }
        }
        return ok;
    }

    /**
     * 添加客户端到集合中，如果已存在则不做处理.
     *
     * @param name 集群名称
     * @param client 集群客户端
     */
    public void addClient(String name, Client client) {
        if (!clientMap.containsKey(name)) {
            ClusterClient clusterClient = new ClusterClient(client, name);
            clientMap.put(name, clusterClient);
            clients.add(clusterClient);
        }
    }

    /**
     * 添加客户端到集合中，如果已存在则不做处理.
     *
     * @param clusterClient 集群客户端
     */
    public void addClient(ClusterClient clusterClient) {
        if (!clientMap.containsKey(clusterClient.getName())) {
            clientMap.put(clusterClient.getName(), clusterClient);
            clients.add(clusterClient);
        }
    }

    /**
     * 注册索引工具类到集群客户端.
     *
     * @param indexTool 索引工具类
     */
    public void registIndexTool(IndexTool indexTool) {
        Class clazz = (Class) ((ParameterizedType) indexTool.getClass().getGenericSuperclass()).getActualTypeArguments()[0];
        for (ClusterClient client : clients) {
            client.addIndexTool(clazz, indexTool);
        }
    }

    /**
     * 随机选择操作集群客户端.
     *
     * @return 集群客户端
     */
    public ClusterClient selectClient() {
        return selectCluster(null);
    }

    /**
     * 根据指定hash键选择操作集群客户端.
     *
     * @param hashKey 选择用hash键
     * @return 集群客户端
     */
    private ClusterClient selectClient(Object hashKey) {
        return selectCluster(hashKey);
    }

    /**
     * 根据指定hash键选择操作集群名称.
     * <br/>未指定hash键时采用随机选择模式.
     *
     * @param hashKey 选择用hash键
     * @return 集群客户端
     */
    private ClusterClient selectCluster(Object hashKey) {
        ClusterClient client = null;
        if (hashKey == null || clusterSelector == null) {
            client = defaultClusterSelector.select(clients, null);
        } else {
            client = clusterSelector.select(clients, hashKey);
        }
        if (ClusterStats.UNAVAILABLE == client.getStats()) {
            for (ClusterClient cc : clients) {
                if (ClusterStats.AVAILABLE == cc.getStats()) {
                    client = cc;
                    break;
                }
            }
        }
        return client;
    }

    /**
     * 判定给定集群是否存在.
     *
     * @param clusterName 集群名称
     * @return 判定结果（true-存在、false-不存在）
     */
    public boolean isExisted(String clusterName) {
        return clientMap.containsKey(clusterName);
    }

    /**
     * 获取指定名称的集群客户端.
     *
     * @param clusterName 集群名称
     * @return 集群客户端
     */
    public ClusterClient getClient(String clusterName) {
        return clientMap.get(clusterName);
    }

    public Map<String, ClusterClient> getClientMap() {
        return clientMap;
    }

    public void setClusterSelector(MultiClusterSelector clusterSelector) {
        this.clusterSelector = clusterSelector;
    }

    public boolean isMultiple() {
        return multiple;
    }

    public void setMultiple(boolean multiple) {
        this.multiple = multiple;
    }
}
